package com.charging.business.constants;

import com.charging.business.dao.FeeDll;
import com.charging.business.enums.GantryFeeErrorEnum;
import com.charging.business.gantry.Feecalc;
import com.charging.common.utils.StringUtils;
import com.sun.jna.Native;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author JerryLu
 * @date 2024/8/6 18:37
 * @description 本省费率下载及加载线程
 */
@Component
@Slf4j
//public class FeeThread extends BaseThread {
public class LibraryThread {
    private final Logger logger = LoggerFactory.getLogger(LibraryThread.class);

    private final String KEY_ALL = "ALL";

    private Map<String, FeeDll> gantryDllMap = new HashMap<>();

    private String gantryModuleFileName;

    @Value("${app.gantryDirPath}")
    private String gantryDirPath;

    @Value("${app.gantryDirReg}")
    private String gantryDirReg;

    @Value("${app.gantryIds:}")
    private String gantryIds;

    private Pattern pattern;

    private Pattern versionPattern;

    // 在构造函数中初始化pattern,只编译一次
    public LibraryThread() {
        try {
            if (StringUtils.isEmpty(gantryDirReg)) {
                gantryDirReg = "libfeecalc_province14_14[0-9]{11}_x86\\.so";
            }
            this.pattern = Pattern.compile(gantryDirReg);
            this.versionPattern = Pattern.compile(gantryDirReg);
        } catch (Exception e) {
            logger.error("正则表达式配置无效: {}", gantryDirReg, e);
            throw new IllegalArgumentException("Invalid regex pattern in configuration", e);
        }
    }


    /**
     * 初始化更新各类数据信息
     *
     * @return void
     * @author JerryLu
     * @date 2024/8/15 13:04
     */
//    @Async("asyncServiceExecutor")
    public void execute() {
//        try {
        Map<String, FeeDll> map = loadFeeDllMap();
        if (null == map || map.isEmpty()) {
            throw new RuntimeException("门架计费模块加载失败");
        }
        gantryDllMap = map;
//            Thread.sleep(0);
//        } catch (InterruptedException e) {
//            // 恢复中断状态
//            Thread.currentThread().interrupt();
//            logger.warn("动态库计费模块线程加载异常: " + e.getMessage());
//        }
//        return new AsyncResult<>("");
    }

    /**
     * 根据gantryId获取对应门架计费动态库
     *
     * @param gantryId
     * @return com.charging.business.dao.FeeDll
     * @author JerryLu
     * @date 2024/8/25 16:16
     */
    public FeeDll getFeeDll(String gantryId) {
        FeeDll feeDll = gantryDllMap.get(gantryId);

        if (null != feeDll) {
            return feeDll;
        }
        return gantryDllMap.get(KEY_ALL);
    }

    private Map<String, FeeDll> loadFeeDllMap() {
        try {
            String gantryModulePath = getGantryModulePath();
            if (StringUtils.isBlank(gantryModulePath)) {
                logger.info("门架计费模块配置信息为空，无法加载门架计费模块");
                return null;
            }
            File gantryModuleFile = new File(gantryModulePath);
            if (!gantryModuleFile.exists()) {
                logger.info(gantryModulePath + "门架计费模块文件不存在，无法加载门架计费模块");
                return null;
            }
            String tempGantryIds = gantryIds;
            Map<String, FeeDll> map = new HashMap<>();
            if (StringUtils.isBlank(tempGantryIds)) {
                FeeDll feeDll = loadFeeDll(null, gantryModuleFile);
                if (null == feeDll) {
                    return null;
                }
                map.put(KEY_ALL, feeDll);
                return map;
            }
            String commaEn = ",";
            String commaCh = "，";
            tempGantryIds = tempGantryIds.replaceAll(commaCh, commaEn);
            String[] gantryIdBuffer = tempGantryIds.split(commaEn);
            for (String gantryId : gantryIdBuffer) {
                FeeDll feeDll = loadFeeDll(gantryId, gantryModuleFile);
                logger.info("{}门架计费模块加载{}", gantryId, null == feeDll ? "失败" : "成功");
                if (null == feeDll) {
                    return null;
                }
                map.put(gantryId, feeDll);
            }
            return map;
        } catch (Throwable t) {
            logger.error(String.valueOf(t), t);
        }
        return null;
    }

    /**
     * 加载门架计费动态库
     *
     * @param gantryId
     * @param gantryModuleFile
     * @return com.ruoyi.business.domain.FeeDll
     * @author JerryLu
     * @date 2024/8/10 16:56
     */
    private FeeDll loadFeeDll(String gantryId, File gantryModuleFile) {
        String gantryModulePath = gantryModuleFile.getAbsolutePath();
        File abFile = gantryModuleFile.getAbsoluteFile();
        String soPath = abFile.getParentFile() + "/";
        FeeDll feeDll = new FeeDll();
        feeDll.setSoPath(soPath);
        Feecalc feecalc = Native.loadLibrary(gantryModulePath, Feecalc.class);
        feeDll.setFeecalc(feecalc);
        byte[] soPathBuffer = StringUtils.getBytes0(soPath);
        byte[] curTollGantry = StringUtils.isBlank(gantryId) ? null : StringUtils.getBytes0(gantryId);
        int i = feecalc.init(curTollGantry, soPathBuffer);
        logger.info("计费模块初始化结果: {}", (i == 0 ? "正常" : "异常"));
        if (GantryFeeErrorEnum.NO_ERROR.getValue() != i) {
            return null;
        }
        feeDll.setDllVersion(getDllVersion(feecalc));
        gantryModuleFileName = gantryModuleFile.getName();
        //动态库初始化后进行一次计费，为后续正式计费提高计费速度
        initFirstTransaction(feeDll);
        return feeDll;
    }

    /**
     * 获取计费模块版本号
     *
     * @param feecalc 计费动态库
     * @return 计费模块版本号
     */
    private String getDllVersion(Feecalc feecalc) {
        try {
            int verLen = 128;
            byte[] version = new byte[verLen];
            int dataVerLen = 128;
            byte[] dataVersion = new byte[dataVerLen];
            int i = feecalc.getLibVersion(version, verLen, dataVersion, dataVerLen);
            if (GantryFeeErrorEnum.NO_ERROR.getValue() == i) {
                return new String(version, StandardCharsets.UTF_8).trim();
            }
            return null;
        } catch (Throwable t) {
            logger.error(String.valueOf(t), t);
            return null;
        }
    }

    /**
     * 定时更新计费模块-1分钟一次
     */
    @Scheduled(cron = "0 0/1 * * * ?")
    public void run() {
        String gantryModulePath = getGantryModulePath();
        if (StringUtils.isBlank(gantryModulePath)) {
            return;
        }
        File gantryModuleFile = new File(gantryModulePath);
        if (!gantryModuleFile.exists() || !gantryModuleFile.isFile()) {
            return;
        }
        String gantryModuleName = gantryModuleFile.getName();
        if (gantryModuleName.equalsIgnoreCase(this.gantryModuleFileName)) {
            //计费模块已加载，不再重复加载
            return;
        }
        Map<String, FeeDll> map = loadFeeDllMap();
        if (null == map || map.isEmpty()) {
            logger.info("计费模块{}加载失败", gantryModuleName);
            return;
        }
        gantryDllMap = map;
        logger.info("计费模块{}更新成功", gantryModuleName);
    }

    /**
     * 动态库初始化后进行一次计费，为后续正式计费提高计费速度
     *
     * @param feeDll
     * @return void
     * @author JerryLu
     * @date 2024/8/10 17:02
     */
    private void initFirstTransaction(FeeDll feeDll) {
        Feecalc.TradingInfo.ByReference tradingInfo = getDefaultTradingInfo();
        Feecalc.FeeInfo.ByReference feeInfo = new Feecalc.FeeInfo.ByReference();
        long start = System.currentTimeMillis();
        int i = feeDll.getFeecalc().getFee(tradingInfo, feeInfo);
        long now = System.currentTimeMillis();
        GantryFeeErrorEnum errorEnum = GantryFeeErrorEnum.getInstanceByValue(i);
        logger.info("初始化获取计费模块费用结果:{}=={},用时{}毫秒", errorEnum, i, now - start);
    }

    private Feecalc.TradingInfo.ByReference getDefaultTradingInfo() {
        Feecalc.TradingInfo.ByReference tradingInfo = new Feecalc.TradingInfo.ByReference();
//        tradingInfo.curTollGantry = StringUtils.getBytes0("G000561001000310010", tradingInfo.curTollGantry);//秦
        tradingInfo.curTollGantry = StringUtils.getBytes0("G000514065000220010", tradingInfo.curTollGantry);//晋
        tradingInfo.curPassTime = StringUtils.getBytes0("2023-10-19T20:18:00", tradingInfo.curPassTime);
        tradingInfo.laneStatus = 1;
        tradingInfo.enTollStationHex = StringUtils.getBytes0("63010198", tradingInfo.enTollStationHex);
        tradingInfo.enPassTime = StringUtils.getBytes0("2023-10-18T07:04:02", tradingInfo.enPassTime);
        tradingInfo.lastGantryHex = StringUtils.getBytes0("000000", tradingInfo.lastGantryHex);
        tradingInfo.lastPasstime = StringUtils.getBytes0("2023-09-27T10:31:36", tradingInfo.lastPasstime);
        tradingInfo.lastGantryFee = 0;
        tradingInfo.lastFeeMileage = 0;
        tradingInfo.issuerId = StringUtils.getBytes0("630100", tradingInfo.issuerId);
        tradingInfo.cardNetwork = StringUtils.getBytes0("0", tradingInfo.cardNetwork);
        tradingInfo.vehicleUserType = 0;
        tradingInfo.axleCount = 2;
        tradingInfo.enAxleCount = 2;
        tradingInfo.tagType = 2;
        tradingInfo.cardType = 0;
        tradingInfo.cardVer = 0;
        tradingInfo.obuSn = StringUtils.getBytes0("6301017200015212", tradingInfo.obuSn);
        tradingInfo.cpuCardId = StringUtils.getBytes0("", tradingInfo.cpuCardId);
        tradingInfo.plateHex = StringUtils.getBytes0("C7E04D455830313200000000", tradingInfo.plateHex);
        tradingInfo.plateColor = 0;
        tradingInfo.vehicleType = 1;
        tradingInfo.enVehicleType = 1;
        tradingInfo.vehicleClass = 0;
        tradingInfo.feeSumLocal = 0;
        tradingInfo.vehicleWeightLimits = 0;
        tradingInfo.totalWeight = 0;
        tradingInfo.vehicleStatusFlag = 255;
        tradingInfo.feeProvBeginHex = StringUtils.getBytes0("000000", tradingInfo.feeProvBeginHex);
        tradingInfo.feeProvMileage = -1;
        tradingInfo.feeProvGantryNum = 0;
        tradingInfo.payFeeSumLocal = 0;
        tradingInfo.realFeeSumLocal = -1;
        tradingInfo.gantryPassNum = 0;
        tradingInfo.gantryPassHex = StringUtils.getBytes0("", tradingInfo.gantryPassHex);
        tradingInfo.Spare1 = 0;
        tradingInfo.Spare2 = 0;
        tradingInfo.Spare3 = 0;
        tradingInfo.Spare4 = StringUtils.getBytes0("", tradingInfo.Spare4);
        tradingInfo.Spare5 = StringUtils.getBytes0("", tradingInfo.Spare5);
        return tradingInfo;
    }

    /**
     * 获取门架计费模块地址
     *
     * @return java.lang.String
     * @author JerryLu
     * @date 2024/8/10 16:56
     */
    private String getGantryModulePath() {
        String path = "";
        if (StringUtils.isBlank(gantryDirPath)) {
            logger.info("门架计费文件夹未配置");
            return null;
        }
        File dir = new File(gantryDirPath);
        if (!dir.exists() || !dir.isDirectory()) {
            boolean flag = dir.mkdirs();
            logger.info("门架计费文件夹不存在{},创建文件夹{}", gantryDirPath, flag ? "成功" : "失败");
            if (!flag) {
                return null;
            }
        }

        // 更新正则表达式为更加具体的模式来匹配.so文件名
//        Pattern pattern = Pattern.compile("libfeecalc_province14_14[0-9]{11}_x86\\.so");

        File[] fileBuffer = dir.listFiles((dir1, name) -> pattern.matcher(name).matches());
        if (fileBuffer == null || fileBuffer.length == 0) {
            logger.info("calc文件夹{}内无门架计费文件", gantryDirPath);
            return null;
        }

        // 选择最新的.so文件
//        File latestSoFile = Arrays.stream(fileBuffer).max(Comparator.comparing(File::getName)).orElse(null);
//        if (latestSoFile == null) {
//            logger.info("门架计费文件夹{}内无门架计费文件", gantryDirPath);
//            return null;
//        }
        // 基于文件名中的版本号部分选择最新的.so文件
        Optional<File> latestSoFile = Arrays.stream(fileBuffer)
                .filter(File::isFile)
                .max(Comparator.comparingLong(file -> this.extractVersionFromFileName(file.getName())));

        if (latestSoFile.isPresent()) {
            return latestSoFile.get().getAbsolutePath();
        } else {
            logger.info("门架计费文件夹{}内无门架计费文件", gantryDirPath);
            return null;
        }
    }

    // 提取文件名中的版本号部分并转换为 long 类型
    private long extractVersionFromFileName(String fileName) {
        // 正则表达式提取版本号部分
        Matcher matcher = versionPattern.matcher(fileName);
        if (matcher.find()) {
            String versionPart = matcher.group(1);
            try {
                return Long.parseLong(versionPart);
            } catch (NumberFormatException e) {
                logger.error("无法解析文件名 {} 中的版本号", fileName, e);
                return 0;
            }
        } else {
            logger.error("文件名 {} 不符合版本号提取模式", fileName);
            // 返回默认值，表示无效的版本号
            return 0;
        }
    }
}
